/**
 * Copyright 2011, Under-Ground Studio.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.edu.zju.vlis.aus.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import android.content.Context;
import android.util.Log;
import cn.edu.zju.vlis.aus.bean.LogRecord;
import cn.edu.zju.vlis.aus.bean.UsageRecord;
import cn.edu.zju.vlis.aus.db.UsageRecordDAO;
import cn.edu.zju.vlis.aus.util.PreferenceHelper;

/**
 * Instance of this class is used to analyze the system log in event buffer, and
 * generate usage records, then store them to the database.
 * 
 * @author ymsong (songyuming1985@gmail.com) 2011-7-23
 */
public class LogcatAnalyzer {
	// commands
	private static final String[] VIEW_CMD = new String[] { "logcat", "-d",
			"-b", "events", "-s", "-v", "time", "am_restart_activity",
			"am_resume_activity", "am_pause_activity" };
	private static final String[] CLEAR_CMD = new String[] { "logcat", "-c",
			"-b", "events" };

	private static Runtime runtime = Runtime.getRuntime();
	private static Stack<LogRecord> stack = new Stack<LogRecord>();
	//
	private UsageRecordDAO dao;
	private Map<String, UsageRecord> usageRecordMap;

	/**
	 * Default constructor.
	 */
	public LogcatAnalyzer(UsageRecordDAO dao) {
		this.dao = dao;
		usageRecordMap = new HashMap<String, UsageRecord>();
	}

	/**
	 * do the analysis.
	 */
	public void analyze(Context context) {
		try {
			if (!PreferenceHelper.isStatisticStartTimeSet(context)) {
				PreferenceHelper.setStatisitcStartTime(context,
						System.currentTimeMillis());
			}
			Log.i("LogcatAnalyzer", "Begin to analyze event log.");
			long time = System.currentTimeMillis();
			synchronized (runtime) {
				InputStream logStream = getLogs();
				doAnalyze(logStream);
				logStream.close();
				clearLogs();
			}
			mergeToDatabase();
			PreferenceHelper.setLastStatisitcTime(context,
					System.currentTimeMillis());
			time = System.currentTimeMillis() - time;
			Log.i("LogcatAnalyzer", "Finish analyzing event log. Time taken:"
					+ time + "ms, stack size = " + stack.size());
			Log.d("LogcatAnalyzer", "stack content = " + stack);
		} catch (IOException e) {
			Log.e("LogcatAnalyzer", e.getMessage(), e);
		}
	}

	// execute the logcat command, and get the logs as InputStream.
	private InputStream getLogs() throws IOException {
		Process logcatProcess = runtime.exec(VIEW_CMD);
		return logcatProcess.getInputStream();
	}

	// execute the clear command.
	private void clearLogs() throws IOException {
		runtime.exec(CLEAR_CMD);
	}

	// read the log stream ,analyze it and fill the usage map.
	private void doAnalyze(InputStream logStream) throws IOException {
		BufferedReader logReader = new BufferedReader(new InputStreamReader(logStream));
		String line = null;
		while ((line = logReader.readLine()) != null) {
			LogRecord record = new LogRecord(line);
			Log.d("LogcatAnalyzer", "1.raw log data" + line);
			Log.d("LogcatAnalyzer", "2.get log record:" + record);
			String packageName = record.getPackageName();
			String activityName = record.getActivityName();
			//
			switch (record.getTag()) {
			case LogRecord.TAG_RESTART:
			case LogRecord.TAG_RESUME: {
				if (!stack.isEmpty()) {
					LogRecord topRecord = stack.peek();
					if (!topRecord.getPackageName().equals(packageName)) {
						LogRecord lastRecord = stack.pop();
						LogRecord firstRecord = null;
						while (!stack.isEmpty()) {
							topRecord = stack.peek();
							if (topRecord.getPackageName().equals(
									lastRecord.getPackageName())) {
								firstRecord = stack.pop();
							} else {
								break;
							}
						}
						if (firstRecord != null) {
							UsageRecord usage = new UsageRecord();
							usage.setPackageName(lastRecord.getPackageName());
							usage.setUseTime((int) (lastRecord.getTime() - firstRecord
									.getTime()) / 1000);
							usage.setLastUse(lastRecord.getTime());
							usage.setUseCount(1);
							mergeToUageMap(usage);
						}
					}
				}
				stack.push(record);
				break;
			}

			case LogRecord.TAG_PAUSE: {
				boolean match = false;
				if (!stack.isEmpty()) {
					LogRecord topRecord = stack.peek();
					if (topRecord.getPackageName().equals(packageName)
							&& topRecord.getActivityName().equals(activityName)
							&& (topRecord.getTag() == LogRecord.TAG_RESTART || topRecord
									.getTag() == LogRecord.TAG_RESUME)) {
						match = true;
						stack.push(record);
					}
				}
				if (!match) {
					Log.w("LogcatAnalyzer", record + " not match!");
				}
				break;
			}
			}
		}
	}

	// merge a usage record to the map.
	private void mergeToUageMap(UsageRecord usage) {
		String packageName = usage.getPackageName();
		UsageRecord record = usageRecordMap.get(packageName);
		if (record == null) {
			usageRecordMap.put(packageName, usage);
		} else {
			record.setUseTime(record.getUseTime() + usage.getUseTime());
			record.setUseCount(record.getUseCount() + 1);
			record.setLastUse(usage.getLastUse());
		}
	}

	// merge the usage record map to the database.
	private void mergeToDatabase() {
		List<UsageRecord> usageRecordList = new ArrayList<UsageRecord>(
				usageRecordMap.values());
		dao.merge(usageRecordList);
		Log.i("LogcatAnalyzer",
				"3.Find and merge " + usageRecordList.size()
						+ " usage records in this analysis, they are:"
						+ usageRecordList);
		usageRecordMap.clear();
	}

}
